/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.properties;
import java.io.IOException;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.text.MessageFormat;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.JTable;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.border.LineBorder;
import javax.swing.table.TableColumn;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.openide.cookies.OpenCookie;
import org.openide.cookies.SaveCookie;
import org.openide.loaders.MultiDataObject;
import org.openide.loaders.FileEntry;
import org.openide.loaders.OpenSupport;
import org.openide.loaders.DataObject;
import org.openide.filesystems.FileObject;
import org.openide.windows.CloneableTopComponent;
import org.openide.windows.TopComponent;
import org.openide.windows.Workspace;
import org.openide.windows.Mode;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.WeakListener;
import org.openide.text.EditorSupport;
import org.openide.NotifyDescriptor;
import org.openide.DialogDescriptor;
import org.openide.TopManager;
/** Support for opening properties files (OpenCookie) in visual editor */
public class PropertiesOpen extends OpenSupport implements OpenCookie {
/** Main properties dataobject */
PropertiesDataObject obj;
PropertyChangeListener modifL;
private PropertiesTableModel tableModel = null;
private Dialog dialog;
private boolean closingLast = false;
/** Constructor */
public PropertiesOpen(PropertiesFileEntry fe) {
super(fe);
this.obj = (PropertiesDataObject)fe.getDataObject();
this.obj.addPropertyChangeListener(new WeakListener.PropertyChange(modifL =
new ModifiedListener()));
}
void setRef(CloneableTopComponent.Ref ref) {
allEditors = ref;
}
/** Only here because of a compiler bug */
public final CloneableTopComponent openCloneableTopComponentPublic() {
return openCloneableTopComponent();
}
/** A method to create a new component. Must be overridden in subclasses.
* @return the cloneable top component for this support
*/
protected CloneableTopComponent createCloneableTopComponent () {
return new PropertiesCloneableTopComponent(obj);
}
PropertiesFileEntry getEntry() {
return (PropertiesFileEntry)entry;
}
/** Opens the table at a given key */
public PropertiesOpenAt getOpenerForKey(PropertiesFileEntry entry, String key) {
return new PropertiesOpenAt(entry, key);
}
public synchronized boolean hasOpenComponent() {
if (closingLast) return false;
java.util.Enumeration en = allEditors.getComponents ();
return en.hasMoreElements ();
}
public PropertiesTableModel getTableModel() {
if (tableModel == null)
tableModel = new PropertiesTableModel(obj);
return tableModel;
}
private synchronized void closeDocuments() {
closingLast = true;
closeEntry((PropertiesFileEntry)obj.getPrimaryEntry());
for (Iterator it = obj.secondaryEntries().iterator(); it.hasNext(); ) {
closeEntry((PropertiesFileEntry)it.next());
}
closingLast = false;
}
private void closeEntry(PropertiesFileEntry entry) {
PropertiesEditorSupport pes = entry.getPropertiesEditor();
if (pes.hasOpenEditorComponent())
return;
else {
pes.close(); // PENDING - shouldn't close the editor support
entry.getHandler().reparseNowBlocking();
}
}
/** Should test whether all data is saved, and if not, prompt the user
* to save.
*
* @return <code>true</code> if everything can be closed
*/
protected boolean canClose () {
SaveCookie savec = (SaveCookie) obj.getCookie(SaveCookie.class);
if (savec != null) {
if (!shouldAskSave())
return true;
MessageFormat format = new MessageFormat(NbBundle.getBundle(PropertiesOpen.class).getString("MSG_SaveFile"));
String msg = format.format(new Object[] { obj.getName()});
NotifyDescriptor nd = new NotifyDescriptor.Confirmation(msg, NotifyDescriptor.YES_NO_CANCEL_OPTION);
Object ret = TopManager.getDefault().notify(nd);
if (NotifyDescriptor.CANCEL_OPTION.equals(ret))
return false;
if (NotifyDescriptor.YES_OPTION.equals(ret)) {
try {
savec.save();
}
catch (IOException e) {
TopManager.getDefault().notifyException(e);
return false;
}
}
}
return true;
}
/** Returns true if closing this editor whithout saving would result in loss of data
* because al least one of the modified files is not open in the code editor.
* Should be called only if the object has SaveCookie
*/
private boolean shouldAskSave() {
// for each entry : if there is a SaveCookie and no open editor component, return true.
// if passed for all entries, return false
PropertiesFileEntry entry = (PropertiesFileEntry)obj.getPrimaryEntry();
SaveCookie savec = (SaveCookie)entry.getCookie(SaveCookie.class);
if ((savec != null) && !entry.getPropertiesEditor().hasOpenEditorComponent())
return true;
for (Iterator it = obj.secondaryEntries().iterator(); it.hasNext(); ) {
entry = (PropertiesFileEntry)it.next();
savec = (SaveCookie)entry.getCookie(SaveCookie.class);
if ((savec != null) && !entry.getPropertiesEditor().hasOpenEditorComponent())
return true;
}
return false;
}
/** Class for opening at a given key. */
public class PropertiesOpenAt implements OpenCookie {
private String key;
private PropertiesFileEntry entry;
PropertiesOpenAt(PropertiesFileEntry entry, String key) {
this.entry = entry;
this.key = key;
}
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void open() {
PropertiesCloneableTopComponent editor = (PropertiesCloneableTopComponent)openCloneableTopComponentPublic();
PropertiesOpen.this.open();
BundleStructure bs = obj.getBundleStructure();
// find the entry
int entryIndex = bs.getEntryIndexByFileName(entry.getFile().getName());
int rowIndex = bs.getKeyIndexByName(key);
if ((entryIndex != -1) && (rowIndex != -1)) {
editor.editCellAt(rowIndex, entryIndex + 1);
}
editor.requestFocus();
}
}
public static class PropertiesCloneableTopComponent extends CloneableTopComponent {
private PropertiesDataObject dobj;
private transient PropertyChangeListener cookieL;
private transient JPanel mainPanel;
private static Image icon = null;
/** The string which will be appended to the name of top component
* when top component becomes modified */
protected String modifiedAppendix = " *";
static final long serialVersionUID =2836248291419024296L;
/** Default constructor for deserialization */
public PropertiesCloneableTopComponent() {
}
/** Constructor
* @param obj data object we belong to
*/
public PropertiesCloneableTopComponent (final PropertiesDataObject obj) {
super (obj);
dobj = obj;
initMe();
}
public Image getIcon () {
if (icon == null)
icon = Toolkit.getDefaultToolkit().getImage(getClass().getResource (
"/org/netbeans/modules/properties/propertiesEditorMode.gif"));
return icon;
}
private void initMe() {
// add to OpenSupport - patch for a bug in deserialization
dobj.getOpenSupport().setRef(getReference());
setName(dobj.getNodeDelegate().getDisplayName());
// listen to saving
dobj.addPropertyChangeListener(new WeakListener.PropertyChange(cookieL =
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (DataObject.PROP_COOKIE.equals(evt.getPropertyName()))
setName(dobj.getNodeDelegate().getDisplayName());
}
}));
initComponents();
// dock into editor mode if possible
Workspace[] currentWs = TopManager.getDefault().getWindowManager().getWorkspaces();
for (int i = currentWs.length; --i >= 0; ) {
Mode editorMode = currentWs[i].findMode(EditorSupport.EDITOR_MODE);
if (editorMode == null) {
editorMode = currentWs[i].createMode(
EditorSupport.EDITOR_MODE, getName(),
EditorSupport.class.getResource(
"/org/openide/resources/editorMode.gif"
)
);
}
editorMode.dockInto(this);
}
}
public HelpCtx getHelpCtx () {
return new HelpCtx (PropertiesCloneableTopComponent.class);
}
public void editCellAt(final int row,final int column) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
((BundleEditPanel)mainPanel).stopEditing();
((BundleEditPanel)mainPanel).getTable().editCellAt(row, column);
}
});
}
/** Inits the subcomponents. */
private void initComponents() {
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout (gridbag);
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.weighty = 1.0;
c.gridwidth = GridBagConstraints.REMAINDER;
mainPanel = new BundleEditPanel(dobj, dobj.getOpenSupport().getTableModel());
gridbag.setConstraints(mainPanel, c);
add (mainPanel);
}
/** Set the name of this top component. Handles saved/not saved state.
* Notifies the window manager.
* @param displayName the new display name
*/
public void setName (final String name) {
String saveAwareName = name;
if (dobj != null)
if (dobj.getCookie(SaveCookie.class) != null)
saveAwareName = name + modifiedAppendix;
else
saveAwareName = name;
if ((saveAwareName != null) && (saveAwareName.equals(getName())))
return;
super.setName(saveAwareName);
}
/** When closing last view, also close the document.
* @return <code>true</code> if close succeeded
*/
protected boolean closeLast () {
if (!dobj.getOpenSupport().canClose ()) {
// if we cannot close the last window
return false;
}
dobj.getOpenSupport().closeDocuments();
return true;
}
/** Is called from the clone method to create new component from this one.
* This implementation only clones the object by calling super.clone method.
* @return the copy of this object
*/
protected CloneableTopComponent createClonedObject () {
PropertiesCloneableTopComponent pctc = new PropertiesCloneableTopComponent (dobj);
/*String PROPERTIES_MODE = "org.netbeans.modules.properties";
Workspace cur = TopManager.getDefault().getWindowManager().getCurrentWorkspace();
Mode m = cur.findMode(PROPERTIES_MODE);
if (m == null) {
m = cur.createMode(PROPERTIES_MODE,
NbBundle.getBundle(PropertiesOpen.class).getString("LAB_PropertiesModeName"),
null);
} */
// PENDING
//m.setBounds(new Rectangle(x, y, width, height));
return pctc;
}
/** This method is called when parent window of this component has focus,
* and this component is preferred one in it.
* Override this method to perform special action on component activation.
* (Typical thing to do here is set performers for your actions)
* Remember to call superclass to
*/
protected void componentActivated () {
}
/**
* This method is called when parent window of this component losts focus,
* or when this component losts preferrence in the parent window.
* Override this method to perform special action on component deactivation.
* (Typical thing to do here is unset performers for your actions)
*/
protected void componentDeactivated () {
}
/** Serialize this top component.
* Subclasses wishing to store state must call the super method, then write to the stream.
* @param out the stream to serialize to
*/
public void writeExternal (ObjectOutput out)
throws IOException {
super.writeExternal(out);
out.writeObject(dobj);
}
/** Deserialize this top component.
* Subclasses wishing to store state must call the super method, then read from the stream.
* @param in the stream to deserialize from
*/
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException {
super.readExternal(in);
dobj = (PropertiesDataObject)in.readObject();
initMe();
}
}
/** Listens to modifications and updates save cookie. */
private final class ModifiedListener implements SaveCookie, PropertyChangeListener {
/** Gives notification that the DataObject was changed.
* @param ev PropertyChangeEvent
*/
public void propertyChange(PropertyChangeEvent ev) {
if ((ev.getSource() == obj) &&
(DataObject.PROP_MODIFIED.equals(ev.getPropertyName()))) {
if (((Boolean) ev.getNewValue()).booleanValue()) {
addSaveCookie();
} else {
removeSaveCookie();
}
}
}
/******* Implementation of the Save Cookie *********/
public void save () throws IOException {
// do saving job
saveDocument();
}
/** Save the document in this thread.
* Create "orig" document for the case that the save would fail.
* @exception IOException on I/O error
*/
public void saveDocument () throws IOException {
final FileObject file = obj.getPrimaryEntry().getFile();
PropertiesFileEntry pfe = (PropertiesFileEntry)obj.getPrimaryEntry();
SaveCookie save = (SaveCookie)pfe.getCookie(SaveCookie.class);
if (save != null)
save.save();
for (Iterator it = obj.secondaryEntries().iterator(); it.hasNext();) {
save = (SaveCookie)((PropertiesFileEntry)it.next()).getCookie(SaveCookie.class);
if (save != null)
save.save();
}
}
/** Adds save cookie to the DO.
*/
private void addSaveCookie() {
if (obj.getCookie(SaveCookie.class) == null) {
obj.getCookieSet().add(this);
}
}
/** Removes save cookie from the DO.
*/
private void removeSaveCookie() {
if (obj.getCookie(SaveCookie.class) == this) {
obj.getCookieSet().remove(this);
}
}
} // end of SavingManager inner class
}